﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Timers;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Input;

using EuphoricEngine;

namespace EuphoricEngine_SpaceInvaders_Demo
{
    class SIGamePlayScreen : GameScreen
    {
        // Keep a list of all aliens and alien fired bullets
        List<Sprite> aliens = new List<Sprite>();
        List<Sprite> alienBolts = new List<Sprite>();

        // Sprites representing the player's ship, and the
        // player's bullet if they have fired it (there can only
        // be one on screen at a time)
        Sprite playerShip;
        Sprite playerBolt;

        // HUD component (Heads Up Display) - score, health, etc.
        HUD hud;

        // A short timer that, when triggered, starts the alien
        // update cycle: moving all aliens down towards the player,
        // and causing a random alien to fire their weapon
        Timer alienUpdateTimer;

        // Triggered when the player dies, to call the function that
        // temporarily pauses the game and decreases the player's number
        // of lives. (When the player gets hit, the game will pause for 
        // one second and respawn the player)
        Timer playerDeathTimer;

        KeyboardState lkbs;
        Random r = new Random();

        // We need to store booleans here because if the spawnPlayerShip(),
        // spawnAlienBolt(), etc. functions were called directly, then there
        // is a chance that they could be called during a Draw() cycle, which
        // would crash the game, as the timers that would be calling them are
        // on a seperate thread then the Draw() function. The Update() function
        // will check for these booleans and spawn the necessary items.
        bool needSpawnPlayerShip = false;
        bool needSpawnAlienBolt = false;
        bool needSpawnPlayerBolt = false;
        bool needDestroyPlayerShip = false;

        // Empty constructor
        public SIGamePlayScreen()
        {
        }

        // Setup the game
        protected override void Load()
        {
            spawnAliens();
            spawnPlayerShip();

            lkbs = Keyboard.GetState();

            hud = new HUD();
            AddComponent(hud);
            hud.DrawOrder = 2; // Put the HUD on top of everything

            // The aliens will be updated once every second
            alienUpdateTimer = new Timer(1000);
            alienUpdateTimer.Elapsed += new ElapsedEventHandler(alienUpdateTimer_Elapsed);
            alienUpdateTimer.Start(); // Start the timer

            // The player death timer will be initialized so it is ready to go if the player
            // gets hit, but it will not be started yet
            playerDeathTimer = new Timer(2000);
            playerDeathTimer.Elapsed += new ElapsedEventHandler(playerDeathTimer_Elapsed);
        }

        // Spawn all the aliens into their grid. Called when the game is starting.
        private void spawnAliens()
        {
            // Left margin from the edge of the screen
            int xOffset = (int)(Engine.GraphicsDevice.PresentationParameters.BackBufferWidth / 2f - (11 * 60) / 2f);

            for (int y = 0; y < 6; y++)
                for (int x = 0; x < 11; x++)
                {
                    Sprite alien = new Sprite(xOffset + x * 60, 50 + y * 50, "Content/alien");
                    AddComponent(alien);
                    aliens.Add(alien);
                }
        }

        // Spawns the player ship to the center of the bottom of the screen
        private void spawnPlayerShip()
        {
            playerShip = new Sprite(0, 0, "Content/ship");
            AddComponent(playerShip);

            playerShip.Position = new Vector2(Engine.GraphicsDevice.PresentationParameters.BackBufferWidth / 2f - playerShip.Width / 2f, Engine.GraphicsDevice.PresentationParameters.BackBufferHeight - 70);

            needSpawnPlayerShip = false;
        }

        // Removes the player ship from the game. Used when the player gets hit
        // and needs to re-spawn in the middle of the screen
        private void destroyPlayerShip()
        {
            RemoveComponent(playerShip);
            playerShip = null;
            needDestroyPlayerShip = false;
        }

        // Called when the death timer finishes, and it is time to re-spawn the
        // player and restart the alien update timer, basically resuming the game
        void playerDeathTimer_Elapsed(object sender, ElapsedEventArgs e)
        {
            playerDeathTimer.Stop();
            alienUpdateTimer.Start();

            needSpawnPlayerShip = true;
        }

        // Called every second by the alien update timer. Causes a random alien to
        // fire, and moves all the aliens down a few pixels towards the player
        void alienUpdateTimer_Elapsed(object sender, ElapsedEventArgs e)
        {
            needSpawnAlienBolt = true;

            foreach (Sprite alien in aliens)
            {
                alien.Position += new Vector2(0, 4);
                if (alien.Position.Y + alien.Height > Engine.GraphicsDevice.PresentationParameters.BackBufferHeight - 65)
                {
                    // Kill the player
                    hud.RegisterPlayerLost();
                    needDestroyPlayerShip = true;
                    alienUpdateTimer.Stop();
                }
            }
        }

        // Called by Update() when needSpawnAlienBolt is "true". Picks a random
        // alien and spawn an energy bolt (bullet) at its position.
        private void spawnAlienBolt()
        {
            if (aliens.Count == 0)
                return;

            int i = r.Next(0, aliens.Count);
            Sprite firing = aliens[i];

            Sprite alienBolt = new Sprite(firing.Position.X + firing.Width / 2, firing.Position.Y + firing.Height, "Content/bolt");
            alienBolts.Add(alienBolt);
            AddComponent(alienBolt);

            needSpawnAlienBolt = false;
        }

        // Called when the player fires. Spawns an energy bolt at the player's position, directed towards the aliens
        private void spawnPlayerBolt()
        {
            Sprite b = new Sprite(playerShip.Position.X + playerShip.Width / 2, playerShip.Position.Y, "Content/bolt");
            AddComponent(b);
            playerBolt = b;

            needSpawnPlayerBolt = false;
        }

        // Update the game
        public override void Update()
        {
            // Remove the player ship if necessary
            if (needDestroyPlayerShip)
                destroyPlayerShip();

            // Spawn the player if necessary
            if (needSpawnPlayerShip)
                spawnPlayerShip();

            // Handle input on the player's ship. Input cannot be handled if there is no
            // player ship, so spawnPlayerShip() is called beforehand
            handleInput();

            // Spawn an alien bullet if necessary
            if (needSpawnAlienBolt)
                spawnAlienBolt();

            // Spawn the player's bullet if necessary. This is effected by handleInput(), so
            // it is called after handleInput().
            if (needSpawnPlayerBolt)
                spawnPlayerBolt();

            updatePlayerBolt(); // Move the player's bullet up
            updateAlienBolts(); // Move the aliens' bullets down

            base.Update();
        }

        private void handleInput()
        {
            KeyboardState kbs = Keyboard.GetState();

            if (playerShip == null) // We can't handle input without a ship
                return;

            // Move the player right or left based on the arrow keys
            if (kbs.IsKeyDown(Keys.Right))
                playerShip.Position += new Vector2(6, 0);
            if (kbs.IsKeyDown(Keys.Left))
                playerShip.Position += new Vector2(-6, 0);

            // Lock the player's ship to the edges of the screen
            playerShip.Position = new Vector2(MathHelper.Clamp(playerShip.Position.X, 0, Engine.GraphicsDevice.PresentationParameters.BackBufferWidth - playerShip.Width), playerShip.Position.Y);

            // Fire if space bar is down, and the player does not have a bullet already on screen
            if (kbs.IsKeyDown(Keys.Space) && playerBolt == null)
                needSpawnPlayerBolt = true;

            lkbs = kbs;
        }

        private void updatePlayerBolt()
        {
            if (playerBolt != null)
            {
                // Move the bolt up
                playerBolt.Position += new Vector2(0, -5);

                // Reset if the bolt goes off the screen
                if (playerBolt.Position.Y + playerBolt.Height < 0)
                {
                    RemoveComponent(playerBolt);
                    playerBolt = null;
                    return;
                }

                // For each alien...
                for (int a = 0; a < aliens.Count; a++)
                {
                    Sprite alien = aliens[a];

                    // If the bolt has collided with it...
                    if ((playerBolt.Position.Y < alien.Position.Y + alien.Height) && (playerBolt.Position.X > alien.Position.X && playerBolt.Position.X < alien.Position.X + alien.Width))
                    {
                        // Kill the alien
                        handleHitOnAlien(alien);
                        return;
                    }
                }
            }
        }

        // Called when the player hits an alien
        private void handleHitOnAlien(Sprite alien)
        {
            // Remove the alien and the bolt
            RemoveComponent(playerBolt);
            playerBolt = null;
            aliens.Remove(alien);
            RemoveComponent(alien);

            // Inform the HUD
            hud.RegisterAlienHit();

            // If all aliens are dead, the player wins- inform the HUD
            if (aliens.Count == 0)
                hud.RegisterPlayerWon();
        }

        private void updateAlienBolts()
        {
            // Fore each alien bolt...
            for (int b = 0; b < alienBolts.Count; b++)
            {
                Sprite bolt = alienBolts[b];

                // Move the bolt
                bolt.Position += new Vector2(0, 5);

                // If it has hit the player ship...
                if (playerShip != null)
                    if ((bolt.Position.X > playerShip.Position.X && bolt.Position.X + bolt.Width < playerShip.Position.X + playerShip.Width)
                        && (bolt.Position.Y + bolt.Height > playerShip.Position.Y && bolt.Position.Y + bolt.Height < playerShip.Position.Y + playerShip.Height))
                    {
                        killPlayer();
                    }

                // Remove the bolt if it goes off screen
                if (bolt.Position.Y - bolt.Height < 0)
                {
                    alienBolts.Remove(bolt);
                    RemoveComponent(bolt);
                }
            }
        }

        private void killPlayer()
        {
            // Kill the player
            hud.RegisterPlayerDied();
            needDestroyPlayerShip = true;
            alienUpdateTimer.Stop();

            // If the player still has more lives, start the respawn timer
            if (!hud.Lost)
                playerDeathTimer.Start();
        }

        // Draws the game
        public override void Draw()
        {
            base.Draw();
        }
    }
}
